/*
 * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved.
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contact: mosaic@fokus.fraunhofer.de
 */
package org.eclipse.mosaic.lib.transform;

import org.eclipse.mosaic.lib.geo.*;
import org.eclipse.mosaic.lib.math.Vector3d;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;

/**
 * Converts WGS84 based coordinates (lat,lon) to cartesian coordinates (x,y)
 * not using a algorithm written by MOSAIC, using the proj4j
 */
public class WictWgs84Projection extends GeoProjection {

    private static final CRSFactory crsFactory = new CRSFactory();
    private static final String EPSG_4326 = "EPSG:4326";
    private static final String WGS_84 = "WGS84";

    private final GeoPoint geoPoint;
    private final UtmPoint utmPoint;
    private final UtmZone zone;
    private final CoordinateReferenceSystem WGS84_CRS;
    private final CoordinateReferenceSystem WGS84_UTM_CRS;
    private final CoordinateReferenceSystem EPSG_CRS = crsFactory.createFromName(EPSG_4326);



    public WictWgs84Projection(GeoPoint origin) {
        this.geoPoint = origin;

        String wgsProj = String.format("+proj=tmerc +lat_0=%.5f +lon_0=%.5f +k=1 +x_0=0 +y_0=0 " +
                "+datum=WGS84 +units=m +no_defs", geoPoint.getLatitude(), geoPoint.getLongitude());
        WGS84_CRS = crsFactory.createFromParameters(WGS_84, wgsProj);

        zone = UtmZone.from(origin);
        String wgsUtmProj = String.format("+proj=utm +zone=%d +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
                zone.number);
        WGS84_UTM_CRS = crsFactory.createFromParameters(WGS_84, wgsUtmProj);

        this.utmPoint = geographicToUtm(geoPoint);
    }

    public WictWgs84Projection(UtmPoint origin) {
        this.utmPoint = origin;

        zone = origin.getZone();
        String wgsUtmProj = String.format("+proj=utm +zone=%d +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
                zone.number);
        WGS84_UTM_CRS = crsFactory.createFromParameters(WGS_84, wgsUtmProj);

        this.geoPoint = utmToGeographic(utmPoint);

        String wgsProj = String.format("+proj=tmerc +lat_0=%.5f +lon_0=%.5f +k=1 +x_0=0 +y_0=0 " +
                "+datum=WGS84 +units=m +no_defs", geoPoint.getLatitude(), geoPoint.getLongitude());
        WGS84_CRS = crsFactory.createFromParameters(WGS_84, wgsProj);
    }


    @Override
    public MutableUtmPoint geographicToUtm(GeoPoint geographic, MutableUtmPoint result) {
        BasicCoordinateTransform transform = new BasicCoordinateTransform(EPSG_CRS, WGS84_UTM_CRS);
        ProjCoordinate pt1 = new ProjCoordinate(geographic.getLongitude(), geographic.getLatitude());
        ProjCoordinate pt2 = new ProjCoordinate();
        transform.transform(pt1, pt2);

        return result.set(pt2.x, pt2.y, geographic.getAltitude(), zone);
    }

    @Override
    public MutableGeoPoint utmToGeographic(UtmPoint utm, MutableGeoPoint result) {
        BasicCoordinateTransform transform = new BasicCoordinateTransform(WGS84_UTM_CRS, EPSG_CRS);
        ProjCoordinate pt1 = new ProjCoordinate(utm.getEasting(), utm.getNorthing());
        ProjCoordinate pt2 = new ProjCoordinate();
        transform.transform(pt1, pt2);

        // x=longitude y=latitude
        return result.set(pt2.y, pt2.x, utm.getAltitude());
    }

    @Override
    public MutableCartesianPoint geographicToCartesian(GeoPoint geographic, MutableCartesianPoint result) {
        BasicCoordinateTransform transform = new BasicCoordinateTransform(EPSG_CRS, WGS84_CRS);
        ProjCoordinate pt1 = new ProjCoordinate(geographic.getLongitude(), geographic.getLatitude());
        ProjCoordinate pt2 = new ProjCoordinate();
        transform.transform(pt1, pt2);

        return result.set(pt2.x, pt2.y, 0);
    }

    @Override
    public MutableGeoPoint cartesianToGeographic(CartesianPoint cartesian, MutableGeoPoint result) {
        BasicCoordinateTransform transform = new BasicCoordinateTransform(WGS84_CRS, EPSG_CRS);
        ProjCoordinate pt1 = new ProjCoordinate(cartesian.getX(), cartesian.getY(), cartesian.getZ());
        ProjCoordinate pt2 = new ProjCoordinate();
        transform.transform(pt1, pt2);

        // x=longitude y=latitude
        return result.set(pt2.y, pt2.x, 0);
    }

    @Override
    public Vector3d utmToVector(UtmPoint utm, Vector3d result) {
        return geographicToVector(utmToGeographic(utm), result);
    }

    @Override
    public MutableUtmPoint vectorToUtm(Vector3d vector, MutableUtmPoint result) {
        return geographicToUtm(vectorToGeographic(vector), result);
    }

    @Override
    public Vector3d geographicToVector(GeoPoint geographic, Vector3d result) {
        getGeoCalculator().distanceBetween(geoPoint, geographic, result);
        return result;
    }

    @Override
    public MutableGeoPoint vectorToGeographic(Vector3d vector3d, MutableGeoPoint result) {
        getGeoCalculator().pointFromDirection(geoPoint, vector3d, result);
        return result;
    }
}
